import cgi
import os
import logging
import utils
import settings
import facebook
import geo.geotypes
import spot_features
import nitro

#import time
#import hashlib

from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.api import users
from google.appengine.api import images
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.api import memcache
#from google.appengine.api import urlfetch

#quota debugging
from google.appengine.api import quota

from django.utils import simplejson
from appengine_utilities.sessions import Session
from db_model import Member, MemberFavorites, MemberFavIndex, GeoSpot, GeoSpotFavorites, SpotValidation, Photo, Comment, SkateSession, PhotoComment, Activity, Sk8Image, Advertisement
from datetime import datetime, timedelta

#globals
dev_env = False
fb_app_id = settings.FACEBOOK_APP_ID
fb_app_secret = settings.FACEBOOK_APP_SECRET
if dev_env:
	fb_app_id = settings.FACEBOOK_LOCALHOST_APP_ID
	fb_app_secret = settings.FACEBOOK_LOCALHOST_APP_SECRET
  
# A helper to do the rendering and to add the necessary
# variables for the _base.htm template
def doRender(handler, tname = 'sk8maps_index.htm', values = { }):
  logging.info("doRender tname: %s" % tname)

  temp = os.path.join(
      os.path.dirname(__file__),
      'templates/' + tname)
  if not os.path.isfile(temp):
    return False

  # Make a copy of the dictionary and add the path and session
  newval = dict(values)
  newval['path'] = handler.request.path
  newval['maps_api_key'] = settings.MAPS_API_KEY
  newval['maps_skatenav_api_key'] = settings.MAPS_SKATENAV_API_KEY
  newval['analytics_id'] = settings.ANALYTICS_ID
  newval['facebook_app_id'] = fb_app_id
  handler.session = Session()
  if 'username' in handler.session:
     newval['username'] = handler.session['username']
  if 'userid' in handler.session:
     newval['userid'] = handler.session['userid']
  if 'displayname' in handler.session:
     newval['displayname'] = handler.session['displayname']

  newval['isadmin'] = False
  if users.is_current_user_admin():
    newval['isadmin'] = True

  newval['current_user'] = None
  if hasattr(handler, "current_user"):
    newval['current_user'] = handler.current_user
  
  outstr = template.render(temp, newval)
  handler.response.out.write(outstr)
  return True
  

# take any image and create all of the different sizes we could possibly need
def AddImage(image_data):
	logging.info("Calling AddImage")
	
	#image = images.Image(image_data)
	#width = image.width
	#height = image.height
	
	new_image = Sk8Image()
	new_image.original = db.Blob(image_data)
	return new_image.put()

def UpdateImage(image_id, image_data):
	logging.info("Calling UpdateImage")
	
	sk8image = Sk8Image.get_by_id(image_id)
	if sk8image:
		logging.info("...got image...updating...")
		
		# force reload/memcache of image
		for size in sk8image.memcache_sizes:
			mem_key = "img_%i_%s" % (image_id, size)
			memcache.delete(mem_key)

		# clear out memcache_sizes
		# TODO: Fix this!!!!
		#while sk8image.memcache_sizes:
		#	sk8image.memcache_sizes.pop()
		#del sk8image.memcache_sizes[0, len(sk8image.memcache_sizes)]
		
		# update original image
		sk8image.original = db.Blob(image_data)
		
		#write out to datastore
		sk8image.put()

def GetImage(image_id, size):
	# always check memcache 1st to see if we already have the image
	mem_key = "img_%i_%s" % (image_id, size)
	img_data = memcache.get(mem_key)
	if img_data is not None:
		#logging.info("GetImage() returning memcached image: " + mem_key)
		return img_data

	# if not in memcache then lookup and add to memcache for quicker retrieval subsequent calls
	sk8image = Sk8Image.get_by_id(image_id)
	if sk8image:
		#logging.info("...Got a sk8image...")
		if size == "thumb":
			img_data = images.resize(sk8image.original, 48, 48)
		elif size == "small":
			img_data = images.resize(sk8image.original, 64, 64)
		elif size == "med":
			img_data = images.resize(sk8image.original, 128, 128)
		elif size == "large":
			img_data = images.resize(sk8image.original, 256, 256)
		elif size == "original":
			img_data = sk8image.original
		else:
			#<width>x<height>
			vars = size.split('x')
			logging.info("...asking for: " + vars[0] + "x" + vars[1])
			img_data = images.resize(sk8image.original, int(vars[0]), int(vars[1]))

		# add to memcache
		if not memcache.add(mem_key, img_data):
			logging.error("img memcache set failed: " + mem_key)
		#else:
		#	logging.info("img memcache add succeeded: " + mem_key)
		
		# update Sk8Image::memcache_sizes stringlist
		# important for when we update an image, clears out old images from memcache.
		if size not in sk8image.memcache_sizes:
			sk8image.memcache_sizes.append(size)
			sk8image.put()
		
		return img_data

	return None

"""
Prefetching reference properties should speed up things quite a bit!!!
See http://blog.notdot.net/2010/01/ReferenceProperty-prefetching-in-App-Engine
Should now fetch with a single datastore get.
"""
def prefetch_refprops(entities, *props):
	#fields = [(entity, prop) for entity in entities for prop in props]
	fields = []
	for prop in props:
		for entity in entities:
			if prop.get_value_for_datastore(entity):
				fields.append((entity,prop))
	ref_keys = [prop.get_value_for_datastore(x) for x, prop in fields]
	ref_entities = dict((x.key(), x) for x in db.get(set(ref_keys)))
	for (entity, prop), ref_key in zip(fields, ref_keys):
		prop.__set__(entity, ref_entities[ref_key])
	return entities

def ClearRecentActivitiesMemcache():
	mem_key = "recent_activity"
	memcache.delete(mem_key)

def GetRecentActivities():
	# always check memcache 1st to see if we already have the image
	mem_key = "recent_activity"
	activity_data = memcache.get(mem_key)
	if activity_data is not None:
		logging.info("GetRecentActivity() returning memcached activity")
		for activity in activity_data:
			time_delta = datetime.now() - activity.timestamp
			activity.time_ago = utils.StrTimeAgo(activity.timestamp, time_delta)
			activity.member.display_name = utils.GetDisplayName(activity.member)
		return activity_data

	logging.info("GetRecentActivities() not in memcache, reading from datastore.")

	# if not in memcache then lookup and add to memcache for quicker retrieval subsequent calls
	activities = Activity.all().order('-timestamp').fetch(20)
	prefetch_refprops(activities, Activity.member, Activity.spot, Activity.photo, Activity.comment)
	
	for activity in activities:
		# add in time_ago field
		time_delta = datetime.now() - activity.timestamp
		activity.time_ago = utils.StrTimeAgo(activity.timestamp, time_delta)
		activity.member.display_name = utils.GetDisplayName(activity.member)

	# add to memcache
	if not memcache.add(mem_key, activities):
		logging.info("memcache set failed: " + mem_key)
	else:
		logging.info("memcache add succeeded: " + mem_key)
	
	return activities

def ClearMemberRecentActivitiesMemcache(username):
	mem_key = "recent_member_activity_%s" % username
	memcache.delete(mem_key)

def GetMemberRecentActivities(username, member_key):
	# always check memcache 1st to see if we already have the image
	mem_key = "recent_member_activity_%s" % username
	activity_data = memcache.get(mem_key)
	if activity_data is not None:
		logging.info("GetMemberRecentActivity() returning memcached activity")
		for activity in activity_data:
			time_delta = datetime.now() - activity.timestamp
			activity.time_ago = utils.StrTimeAgo(activity.timestamp, time_delta)
			activity.member.display_name = utils.GetDisplayName(activity.member)
		return activity_data

	logging.info("GetMemberRecentActivities() not in memcache, reading from datastore.")

	# if not in memcache then lookup and add to memcache for quicker retrieval subsequent calls
	activities = Activity.all().filter('member =', member_key).order('-timestamp').fetch(20)	
	prefetch_refprops(activities, Activity.member, Activity.spot, Activity.photo, Activity.comment)
	
	for activity in activities:
		# add in time_ago field
		time_delta = datetime.now() - activity.timestamp
		activity.time_ago = utils.StrTimeAgo(activity.timestamp, time_delta)
		activity.member.display_name = utils.GetDisplayName(activity.member)

	# add to memcache
	if not memcache.add(mem_key, activities):
		logging.info("memcache set failed: " + mem_key)
	else:
		logging.info("memcache add succeeded: " + mem_key)
	
	return activities		

def ClearMemberMemcache(user_id):
	mem_key = "member_%i" % user_id
	memcache.delete(mem_key)

def GetMember(user_id):
	#check memcache first
	mem_key = "member_%i" % user_id
	member_data = memcache.get(mem_key)
	if member_data is not None:
		logging.info("GetMember() returning memcached member")
		return member_data

	logging.info("GetMember() not in memcache, reading from datastore.")
	member = Member.get_by_id(user_id)
	if member:
		# add to memcache
		if not memcache.add(mem_key, member):
			logging.info("memcache set failed: " + mem_key)
		else:
			logging.info("memcache add succeeded: " + mem_key)
		return member
	
	return None

def GetFeaturedSpots():
	featured_spots_array = [553015, 163004, 182007, 686007]
	#featured_spots_array = [164, 388]
	featured_spots = []
	for index, spot_id in enumerate(featured_spots_array):
		spot = GeoSpot.get_by_id(spot_id)
		image_url = "images/featured%i.jpg" % (index + 1)
		if spot:
			featured_spots.append({'id': spot.key().id(), 'name': spot.name, 'image_url': image_url})
		else:
			featured_spots.append({'id': 0, 'name': 'unknown spot', 'image_url': image_url})
	
	return featured_spots
	
class OriginalBaseHandler(webapp.RequestHandler):
	@property
	def current_user(self):
		if not hasattr(self, "_current_user"):
			self._current_user = None
			cookie = facebook.get_user_from_cookie(self.request.cookies, fb_app_id, fb_app_secret)
			if cookie:
				# Store a local instance of the user data so we don't need
				# a round-trip to Facebook on every request
				member = None
				members = Member.all().filter('uid =', cookie["uid"]).fetch(1)
				if len(members):
					member = members[0]
					
				if not member:
					graph = facebook.GraphAPI(cookie["access_token"])
					profile = graph.get_object("me")
					uname = profile["name"].replace(" ", "")
					uname = uname.lower()
					member = Member(username=uname)
					member.nickname = profile["name"]
					member.uid = str(profile["id"])
					member.profile_url = profile["link"]
					member.access_token = cookie["access_token"]
					member.put()
				elif member.access_token != cookie["access_token"]:
					member.access_token = cookie["access_token"]
					member.put()
					
				self._current_user = member
			else:
				# no cookie, see if we have a cur_user in session
				self.session = Session()
				if 'userid' in self.session:
					member = Member.get_by_id(int(self.session['userid']))
					if member:
						uid = getattr(member, "uid", "") #django templates need uid string for showing member options
						member.uid = uid
						member.put()
						self._current_user = member

		if self._current_user:
			self.session = Session()
			self.session["displayname"] = utils.GetDisplayName(self._current_user)
			self.session["username"] = self._current_user.username
			self.session["userkey"] = self._current_user.key()
			self.session["userid"] = self._current_user.key().id()
					
		return self._current_user
    

class BaseHandler(webapp.RequestHandler):
	"""Provides access to the active Facebook user in self.current_user

	The property is lazy-loaded on first access, using the cookie saved
	by the Facebook JavaScript SDK to determine the user ID of the active
	user. See http://developers.facebook.com/docs/authentication/ for
	more information.
	"""
	@property
	def current_user(self):
		if not hasattr(self, "_current_user"):
			self._current_user = None
			
			cookie = facebook.get_user_from_cookie(self.request.cookies, fb_app_id, fb_app_secret)
			if cookie:
				# Store a local instance of the user data so we don't need
				# a round-trip to Facebook on every request
				member = None
				members = Member.all().filter('uid =', cookie["uid"]).fetch(1)
				if len(members):
					member = members[0]
					
				if not member:
					graph = facebook.GraphAPI(cookie["access_token"])
					profile = graph.get_object("me")
					uname = profile["name"].replace(" ", "")
					uname = uname.lower()
					member = Member(username=uname)
					member.nickname = profile["name"]
					member.uid = str(profile["id"])
					member.profile_url = profile["link"]
					member.access_token = cookie["access_token"]
					member.put()
					
					#new member, add a join activity
					activity = Activity(type="join")
					activity.member = member.key()
					activity.put()
					ClearRecentActivitiesMemcache()
					ClearMemberRecentActivitiesMemcache(member.username)
					
				elif member.access_token != cookie["access_token"]:
					member.access_token = cookie["access_token"]
					member.put()
				
				self._current_user = member
			else:
				# no Facebook cookie, go ahead and see if we have a session for normal user
				self.session = Session()
				if 'userid' in self.session:
					member = Member.get_by_id(int(self.session['userid']))
					if member:
						uid = getattr(member, "uid", "") #django templates need uid string for showing member options
						member.uid = uid
						member.put()
						self._current_user = member

		if self._current_user:
			self.session = Session()
			self.session["displayname"] = utils.GetDisplayName(self._current_user)
			self.session["username"] = self._current_user.username
			self.session["userkey"] = self._current_user.key()
			self.session["userid"] = self._current_user.key().id()
				
		return self._current_user

	
"""
Main Page Handler
=================
"""					 
#class MainPage(webapp.RequestHandler):
class MainPage(BaseHandler):
	def get(self):	
		activities = GetRecentActivities()
		
		#start = quota.get_request_cpu_usage()
		featured_spots = GetFeaturedSpots()
		#end = quota.get_request_cpu_usage()
		#logging.info("GetFeaturedSpots() cost %d megacycles." % (start - end))

		# summarize the activities (groups photos and comments)
		"""
		num_activities = len(activities)
		if num_activities >= 2:
			index = 0
			while index < (num_activities - 2):
				activity = activities[index];
				next_activity = activities[index+1];
				if activity.type == next_activity.type && activity.member.key() == next_activity.member.key():
					# Found a match! Look forward until we run into a different type OR reach the end of the array
					num_matches = 1
					while 
					activity.num_matches = num_matches
					
				index += 1
		"""				
			
		template_values = {
			'activities': activities,
			'featured_spots': featured_spots
			}

		self.session = Session()
		if 'userid' in self.session:
			member_info = GetMember(self.session['userid'])
			if member_info:
				member_info.member_since = utils.StrDate(member_info.join_date)
				member_info.display_name = utils.GetDisplayName(member_info)
				template_values['member'] = member_info

				fav_spots = []
				user_favorites = GeoSpotFavorites.all().filter('userid =', member_info.key().id()).fetch(1)
				if len(user_favorites):
					fav_spot_ids = user_favorites[0].favorites
					if len(fav_spot_ids):
						fav_spots = GeoSpot.get_by_id(fav_spot_ids)
				template_values['fav_spots'] = fav_spots
				
		doRender(self, "sk8maps_index.html", template_values)

class MainPageTest(OriginalBaseHandler):
	def get(self):
		logging.info("=====================MainTestPage=====================")	
		template_values = {}

		self.session = Session()
		if 'userid' in self.session:
			member_info = GetMember(self.session['userid'])
			if member_info:
				member_info.member_since = utils.StrDate(member_info.join_date)
				member_info.display_name = utils.GetDisplayName(member_info)
				template_values['current_user'] = member_info

				fav_spots = []
				user_favorites = GeoSpotFavorites.all().filter('userid =', member_info.key().id()).fetch(1)
				if len(user_favorites):
					fav_spot_ids = user_favorites[0].favorites
					if len(fav_spot_ids):
						fav_spots = GeoSpot.get_by_id(fav_spot_ids)
				template_values['fav_spots'] = fav_spots
				
		logging.info("checking current_user")
		if self.current_user:
			logging.info("got current_user")
			template_values['username'] = self.current_user.username
			template_values['displayname'] = utils.GetDisplayName(self.current_user)
		else:
			logging.info("NO current_user")
					
		doRender(self, "sk8maps_index_test.html", template_values)

"""
Map Index Handler
================
"""
class MapIndexHandler(BaseHandler):
	def get(self):
		logging.info("==========================LoadingMapIndex====================================")
		template_values = { 'current_user': self.current_user }
		doRender(self, "sk8maps_map_index.html", template_values)

"""
Map Handler
===========
"""
class MapHandler(BaseHandler):
	def get(self):	
		template_values = {	}
		doRender(self, "sk8maps_map.html", template_values)

"""
Spots Handler
=============
"""
class SpotsHandler(BaseHandler):
	def get(self):
		sort_by = "alpha"
		sort = self.request.get('sort')
		if sort:
			sort_by = sort

		next_cursor = None
		next_cursor = self.request.get('next')
		
		query = None
		if sort_by == "date":
			query = GeoSpot.all().order('-date_added')
		else:
			query = GeoSpot.all().order('normalized_name')

		if next_cursor:
			query.with_cursor(next_cursor)
			
		spots = query.fetch(50)
		last_cursor = query.cursor()

		# check to see if we even have any more spots for a NEXT link...
		has_next = False
		query.with_cursor(last_cursor)
		next_spots = query.fetch(1)
		if len(next_spots):
			has_next = True
			
		for spot in spots:
			# add in time_ago field
			time_delta = datetime.now() - spot.date_added
			spot.time_ago = utils.StrTimeAgo(spot.date_added, time_delta)
	
		template_values = { 'spots': spots,
							'sort_by': sort_by,
							'last_cursor': last_cursor,
							'has_next': has_next }
		doRender(self, "sk8maps_spots.html", template_values)

"""
Members Handler
=============
"""
class MembersHandler(BaseHandler):
	def get(self):
		sort_by = "alpha"
		sort = self.request.get('sort')
		if sort:
			sort_by = sort

		next_cursor = None
		next_cursor = self.request.get('next')
		
		query = None
		if sort_by == "date":
			query = Member.all().order('join_date')
		else:
			query = Member.all().order('username')

		if next_cursor:
			query.with_cursor(next_cursor)
			
		members = query.fetch(50)
		last_cursor = query.cursor()

		#prefetch reference properties so we only do a single datastore get
		prefetch_refprops(members, Member.avatar)

		# check to see if we even have any more members for a NEXT link...
		has_next = False
		query.with_cursor(last_cursor)
		next_members = query.fetch(1)
		if len(next_members):
			has_next = True
			
		for member in members:
			member.member_since = utils.StrDate(member.join_date)
			member.display_name = utils.GetDisplayName(member)
	
		template_values = { 'members': members,
							'sort_by': sort_by,
							'last_cursor': last_cursor,
							'has_next': has_next }
		doRender(self, "sk8maps_members.html", template_values)

"""
Login Handler
=============
"""    
class LoginHandler(BaseHandler):
    def get(self):
        doRender(self, "sk8maps_login.html")
        
    def post(self):
        self.session = Session()
        username = self.request.get("username")
        pwd = self.request.get("pwd")
        logging.info("Checking account=" + username)
        
        self.session.delete_item("displayname")
        self.session.delete_item("username")
        self.session.delete_item("userkey")
        self.session.delete_item("userid")
        self.session.delete_item("nitro_session_key")
        
        if pwd == '' or username == '':
            doRender(
              self,
              'sk8maps_login.html',
              {'error' : 'Please fill in Username and Password'} )
            return

        #make sure username is lowercase
        username = username.lower()

        que = db.Query(Member).filter('username =', username)
        ret_members = que.fetch(limit=1)
        if len(ret_members) == 0 :
            doRender(
              self,
              'sk8maps_login.html',
              {'error' : 'Account doesnt exist'} )
            return
        
        member = ret_members[0]
        salt_pwd = member.salt + pwd;
        hashed_pwd = utils.CreateSHA1(salt_pwd)
        if member.pwd == hashed_pwd:
            logging.info("hashed pwd matches!!!")
            self.session["displayname"] = utils.GetDisplayName(member)
            self.session["username"] = username
            self.session["userkey"] = member.key()
            self.session["userid"] = member.key().id()

			# Login to Nitro and store off nitro_session_key
            self.session["nitro_session_key"] = nitro.user_login(username)
            logging.info("nitro_session_key = %s" % (self.session["nitro_session_key"]))
            
            self.redirect('/')
        else:
            logging.info("hashed pwd NO match!!!")
            doRender(self, 'sk8maps_login.html', {'error': 'Incorrect Password'})

"""
Logout Handler
==============
"""            
class LogoutHandler(webapp.RequestHandler):
    def get(self):
        self.session = Session()
        self.session.delete_item("displayname")
        self.session.delete_item("username")
        self.session.delete_item("userkey")
        self.session.delete_item("userid")
        self.session.delete_item("spotid")
        self.session.delete_item("nitro_session_key")
        self.redirect('/')

"""
Sign Up Handler
===============
"""        
class SignUpHandler(BaseHandler):
    def get(self):
        doRender(self, "sk8maps_signup.html")

    def post(self):
        self.session = Session()
        uname = self.request.get("username")
        pwd = self.request.get("pwd")
        verify_pwd = self.request.get("verify_pwd")
        email = self.request.get("email")
        
        # make sure they entered everything
        if pwd == '' or verify_pwd == '' or uname == '' or email == '':
            doRender(
              self,
              'sk8maps_signup.html',
              {'error' : 'Please fill in all fields'} )
            return
        
        #make sure username is lowercase
        uname = uname.lower()
                  
        #make sure username doesn't already exist
        que = db.Query(Member).filter('username =', uname)
        results = que.fetch(limit=1)
        if len(results) > 0 :
            doRender(
              self,
              'sk8maps_signup.html',
              {'error' : 'Account Already Exists'} )
            return

        #make sure password match
        if pwd != verify_pwd:
            doRender(
              self,
              'sk8maps_signup.html',
              {'error' : 'Passwords don\'t match'} )
            return
		
		# Add the new member	
        newmember = Member(username=uname)
        newmember.salt = utils.CreateSalt()
        salt_pwd = newmember.salt + pwd;
        newmember.pwd = utils.CreateSHA1(salt_pwd)
        newmember.email = email
        avatar_data = self.request.get("avatar")
        if avatar_data != '':
            newmember.avatar = AddImage(avatar_data)
            """
            image = images.Image(avatar_data)
            width = image.width
            height = image.height
            if width > 200 or height > 200:
                larger = (width if width > height else height)
                ratio = 200.0/larger
                width = int(width * ratio)
                height = int(height * ratio)
                logging.info("USER AVATAR RESIZED: " + str(width) + ", " + str(height))
                #image = image.resize(width, height)
                
            avatar = images.resize(avatar_data, width, height)
            newmember.avatar = db.Blob(avatar)
            """

        pkey = newmember.put()
        
        # Update session data
        self.session['displayname'] = utils.GetDisplayName(newmember)
        self.session['username'] = uname
        self.session['userkey'] = pkey
        self.session['userid'] = pkey.id()
        self.session["nitro_session_key"] = nitro.user_login(uname)
        
        # Add a "join" activity
        activity = Activity(type="join")
        activity.member = self.session['userkey']
        activity.put()
        ClearRecentActivitiesMemcache()
        ClearMemberRecentActivitiesMemcache(self.session["username"])
        
        self.redirect('/')


class ChangeAvatarHandler(webapp.RequestHandler):		
	def post(self):
		self.session = Session()
		if 'username' not in self.session:
			doRender(self, 'profile?user=', {'error': 'Must be logged in to add a new spot.'})
			return

		member = Member.get(self.session['userkey'])
		if member:
			avatar_data = self.request.get("avatar")
			if avatar_data != '':
				if member.avatar:
					UpdateImage(member.avatar.key().id(), avatar_data)
				else:
					member.avatar = AddImage(avatar_data)
					member.put()
					
			"""
				image = images.Image(avatar_data)
				width = image.width
				height = image.height
				if width > 200 or height > 200:
					larger = (width if width > height else height)
					ratio = 200.0/larger
					width = int(width * ratio)
					height = int(height * ratio)
					logging.info("USER AVATAR RESIZED: " + str(width) + ", " + str(height))
					#image = image.resize(width, height)

				avatar = images.resize(avatar_data, width, height)
				member.avatar = db.Blob(avatar)
				member.put()
			"""
		
		profile_url = "/profile?name=" + self.session['username']
		logging.info("CHANGE AVATAR REDIRECTING TO: " + profile_url)
		self.redirect(profile_url)		
		
"""
Admin Page Handler
==================
"""		
class AdminPage(BaseHandler):
	def get(self):
		admin = False
		if users.is_current_user_admin():
			admin = True

		user = users.get_current_user()
		admin_username = "unknown user"
		if user:
			admin_username = user.nickname()
		else:
			self.redirect(users.create_login_url(self.request.uri))

		unvalidated_spots = GeoSpot.all().filter('validated =', False).order('-date_added')
		login_url = users.create_login_url("/admin")
		logout_url = users.create_logout_url("/admin")
		
		admin_values = {
			'admin': admin,
			'admin_username': admin_username,
			'unvalidated_spots': unvalidated_spots,
			'login_url': login_url,
			'logout_url': logout_url
		}

		doRender(self, 'sk8maps_admin.html', admin_values)

	def post(self):
		if users.is_current_user_admin():
			spot_id = int(self.request.get('spot_id'))
			spot_name = self.request.get('spot_name')
			spot_desc = self.request.get('spot_desc')
			
			spot = GeoSpot.get_by_id(spot_id)
			if spot is None:
				self.redirect('/admin')

			spot.name = spot_name
			spot.normalized_name = spot_name.lower()
			spot.desc = spot_desc
			
			try:
				spot.put()
			except:
				logging.error("Error updating spot from ADMIN page.")
				
		self.redirect('/admin')

"""
Add Spot Handler
================
"""		
class AddSpotHandler(webapp.RequestHandler):
	def post(self):
		self.session = Session()
		if 'username' not in self.session:
			doRender(self, 'sk8maps_spots.html', {'error': 'Must be logged in to add a new spot.'})
			return

		lat = float(self.request.get('spot_lat'))
		lng = float(self.request.get('spot_long'))

		# Make sure we don't already have this exact geospot
		spot_check = GeoSpot.all().filter('location =', db.GeoPt(lat, lng)).fetch(1)
		if len(spot_check):
			logging.error("Error saving GeoSpot: DUPLICATE.");
			self.response.set_status(400)
			self.response.out.write("Duplicate Spot Coordinates")
			return
			
		# Add the Spot
		spot = GeoSpot(location = db.GeoPt(lat, lng))
		spot.update_location() #synchronize the entity's underlying geocell indexing properties
		spot.name = self.request.get('spot_name')
		spot.normalized_name = spot.name.lower()
		spot.desc = self.request.get('spot_desc')
		spot.added_by = self.session['userkey']
		spot.validated = False
		
		# Features come in already masked (see spot_features.py)
		spot.features = int(self.request.get('spot_features'))
		spot.tranny_features = int(self.request.get('spot_tranny_features'))
		spot.street_features = int(self.request.get('spot_street_features'))
		spot.longboard_features = int(self.request.get('spot_longboard_features'))
		spot.misc_features = int(self.request.get('spot_misc_features'))
		
		try:
			spot_key = spot.put()

			# Add "add_spot" Activity
			activity = Activity(type="add_spot")
			activity.member = self.session['userkey']
			activity.spot = spot_key
			activity.put()
			ClearRecentActivitiesMemcache()
			ClearMemberRecentActivitiesMemcache(self.session["username"])

			
		except:
			logging.error("Error saving GeoSpot.  Name: %s" % spot.name)
			doRender(self, 'sk8maps_spots.html', {'error': 'Must be logged in to add a new spot.'})
			return

		# Nitro ADD_NEW_SPOT
		nitro.add_new_spot(self.session["nitro_session_key"])
		
		# Success!!!  Return the spot id so we can add to map.
		str_spot_key_id = str(spot_key.id())
		logging.info("DEBUG ADDSPOTHANDLER: " + str_spot_key_id)
		self.response.set_status(200, str_spot_key_id)
		self.response.out.write(str_spot_key_id)

"""
Spot Handler
============
"""
class SpotHandler(BaseHandler):
	def get(self):
		# store off the spot id in our session so we don't have to pass it around everywhere.
		self.session = Session()
		self.session.delete_item("spotid")

		id = self.request.get('id')
		if id:
			#spot = db.get(db.Key(id))
			if int(id) == 0:
				self.error(404)
				return;
				
			spot = GeoSpot.get_by_id(int(id))
			if spot is None:
				self.error(404)
				return	

			logging.info("Showing spot: " + spot.name)
			
			self.session["spotid"] = id
			
			# passed template values
			tvalues = {}
			tvalues['spot'] = spot
			tvalues['spot_lat'] = str(spot.location.lat)
			tvalues['spot_lon'] = str(spot.location.lon)
			tvalues['spot_features'] = get_features_string(spot)
			
			# get photos for this spot
			
			start = quota.get_request_cpu_usage()
			
			query = db.Query(Photo).filter('spot =', spot.key()).order('-timestamp')
			ret_photos = query.fetch(limit=100)

			#prefetch reference properties so we only do a single datastore get
			prefetch_refprops(ret_photos, Photo.spot, Photo.added_by, Photo.image)

			#coming from a link on an image, go ahead and set the index so page can open in Yoxviewer.
			tvalues['open_photo'] = False
			img_id = self.request.get('img_id')
			if img_id:
				for index, sk8photo in enumerate(ret_photos):
					if sk8photo.image.key().id() == int(img_id):
						tvalues['open_photo'] = True
						tvalues['open_photo_index'] = index
						break;

			end = quota.get_request_cpu_usage()
			logging.info("SpotHandler() photos cost %d megacycles." % (start - end))
			
			# special handling for rendering columns correctly in template
			"""
			ncols = 3
			for i, d in enumerate(ret_photos):
				d.firstcol = (i % ncols == 0)
				d.lastcol = (i % ncols == ncols-1)
			tvalues['photo_blanks'] = [None]*((ncols-len(ret_photos)%ncols)%ncols)
			tvalues['photo_cols'] = ncols
			"""
			tvalues['photos'] = ret_photos
			
			# get comments for this spot
			query = db.Query(Comment).filter('spot =', spot.key()).order('-timestamp')
			ret_comments = query.fetch(limit=50)

			#prefetch reference properties so we only do a single datastore get
			prefetch_refprops(ret_comments, Comment.spot, Comment.added_by)
			
			for comment in ret_comments:
				# add in time_ago field
				time_delta = datetime.now() - comment.timestamp
				comment.time_ago = utils.StrTimeAgo(comment.timestamp, time_delta)
				comment.added_by.display_name = utils.GetDisplayName(comment.added_by)
			
			tvalues['comments'] = ret_comments
			
			# get skate sessions for this spot
			"""
			query = db.Query(SkateSession).filter('spot =', spot.key()).order('-timestamp')
			ret_skate_sessions = query.fetch(limit=10)			
			skate_sessions_html = CreateSkateSessionHtmlArray(ret_skate_sessions)
			tvalues['skate_sessions_html'] = skate_sessions_html
			"""

			# check if this is a favorite spot
			tvalues['spot_favorite'] = 0
			if 'userid' in self.session:
				spot_favorites_array = GeoSpotFavorites.all().filter('userid =', self.session['userid']).fetch(1)
				if len(spot_favorites_array):
					spot_favorites = spot_favorites_array[0]
					if spot.key().id() in spot_favorites.favorites:
						tvalues['spot_favorite'] = 1
			
			# Check for any errors that may have come in from a redirect (see AddPhotoHandler and AddCommentHandler)
			comment_error = self.request.get('comment_error')
			if comment_error:
				tvalues['comment_error'] = comment_error

			photo_error = self.request.get('photo_error')
			if photo_error:
				tvalues['photo_error'] = photo_error

			# can user edit this spot?			
			tvalues['can_edit'] = False
			if users.is_current_user_admin():
				tvalues['can_edit'] = True
			else:
				# original creator of spot has edit access
				if 'userkey' in self.session:
					if spot.added_by.key() == self.session['userkey']:
						tvalues['can_edit'] = True
			
			doRender(self, "sk8maps_spot.html", tvalues)
			return

		doRender(self, "sk8maps_index.html")

"""
Edit Spot Handler
============
"""
class EditSpotHandler(BaseHandler):
	def get(self):
		# store off the spot id in our session so we don't have to pass it around everywhere.
		self.session = Session()
		self.session.delete_item("spotid")

		id = self.request.get('id')
		if id:
			if int(id) == 0:
				self.error(404)
				return;
				
			spot = GeoSpot.get_by_id(int(id))
			if spot is None:
				self.error(404)
				return	

			logging.info("Editing Spot: id=" + id + ", name=" + spot.name)

			# make sure this user can edit spot
			if users.is_current_user_admin() == False:
				# original creator of spot has edit access
				if 'userkey' in self.session:
					if spot.added_by.key() != self.session['userkey']:
						self.error(403) # access denied
						return
		
			self.session["spotid"] = id
			
			# passed template values
			tvalues = {}
			tvalues['spot'] = spot
			tvalues['spot_lat'] = str(spot.location.lat)
			tvalues['spot_lon'] = str(spot.location.lon)

			doRender(self, "sk8maps_edit_spot.html", tvalues)
			return

		self.redirect('/spot?id=' + id)

	def post(self):
		id = self.request.get("spotid")

		if int(id) == 0:
			self.error(404)
			return;
			
		spot = GeoSpot.get_by_id(int(id))
		if spot is None:
			self.error(404)
			return	

		# make sure this user can edit spot
		if users.is_current_user_admin() == False:
			# original creator of spot has edit access
			self.session = Session()
			if 'userkey' in self.session:
				if spot.added_by.key() != self.session['userkey']:
					self.error(403) # access denied
					return
			
		name = self.request.get("name")
		desc = self.request.get("desc")
		tranny_features = self.request.get("tranny_features")
		street_features = self.request.get("street_features")
		longboard_features = self.request.get("longboard_features")
		misc_features = self.request.get("misc_features")
		
		logging.info("Updating Spot: id=" + id + ", name=" + name)

		spot.name = name
		spot.normalized_name = name.lower()
		spot.desc = desc
		spot.tranny_features = int(tranny_features)
		spot.street_features = int(street_features)
		spot.longboard_features = int(longboard_features)
		spot.misc_features = int(misc_features)
		
		spot.put()
				
		self.redirect('/spot?id=' + id)
		
"""
Add Photo Handler
=================
"""	
class AddPhotoHandler(webapp.RequestHandler):
	def post(self):
		self.session = Session()
		if 'spotid' not in self.session:
			logging.info("AddPhotoHander redirecting to login")
			self.redirect("/login")
			return
			
		url = '/spot?id=' + str(self.session['spotid'])

		if 'username' not in self.session:
			doRender(self, url, {'photo_error': 'Must be logged in to add a new photo.'})
			return

		str_spot_id = self.request.get("spot")
		spot = GeoSpot.get_by_id(int(str_spot_id))
		if spot is None:
			url += "&photo_error=%s" % ("Cant find spot for specified ID.")
			self.redirect(url)
			return
			
		logging.info("adding photo for spot: " + str_spot_id)

		photo_data = self.request.get("photo")
		if photo_data == '':
			logging.info("got NULL photo")
			#url += "&photo_error=%s" % ("Cant find spot for specified ID.")
			self.redirect(url)
			return

		photo_description = self.request.get("photo_description")
		
		logging.info("creating new photo object")

		# Add a "Photo"
		photo = Photo()
		photo.spot = spot.key()
		photo.added_by = self.session['userkey']
		photo.image = AddImage(photo_data)
		photo.desc = photo_description
		
		try:
			photo_key = photo.put()
		except:
			logging.error("Error saving spot photo at: %s" % str_spot_id)
			url += "&photo_error=%s" % ("Error saving spot photo.")
			self.redirect(url)
			return

		# Nitro add photo
		nitro.add_spot_photo(self.session["nitro_session_key"])

		# Add an "add_photo" activity
		activity = Activity(type='spot_photo')
		activity.member = self.session['userkey']
		activity.spot = spot.key()
		activity.photo = photo_key
		
		try:
			activity.put()
			ClearRecentActivitiesMemcache()
			ClearMemberRecentActivitiesMemcache(self.session["username"])
			
		except:
			logging.error("Error saving spot photo activity at: %s" % str_spot_id)
			url += "&photo_error=%s" % ("Error saving spot photo activity.")
			self.redirect(url)
			return
			
		self.redirect(url)

"""
Add Comment Handler
===================
"""
class AddCommentHandler(webapp.RequestHandler):
	def post(self):
		self.session = Session()
		if 'spotid' not in self.session:	
			logging.info("AddCommentHander redirecting to login")
			self.redirect("/login")
			return

		url = '/spot?id=' + str(self.session['spotid'])
		logging.info("add comment url: %s" % url)

		if 'username' not in self.session:
			url += "&comment_error=%s" % ("Must be logged in to add a new comment.")
			self.redirect(url)
			return

		str_spot_id = self.request.get("spot")
		spot = GeoSpot.get_by_id(int(str_spot_id))
		if spot is None:
			url += "&comment_error=%s" % ("Cant find spot for specified ID.")
			self.redirect(url)
			return
			
		logging.info("adding comment for spot: " + str_spot_id)
		comment_text = cgi.escape(self.request.get("comment"))
		if comment_text == '':
			logging.info("got empty comment")
			#url += "&comment_error=%s" % ("Please enter a comment")
			self.redirect(url)
			return

		# Add a "Comment"
		comment = Comment()
		comment.spot = spot.key()
		comment.added_by = self.session['userkey']
		comment.comment = comment_text
		try:
			comment_key = comment.put()

			# Nitro add comment
			nitro.add_spot_comment(self.session["nitro_session_key"])
			
			# Add an "add_comment" activity
			activity = Activity(type='spot_comment')
			activity.member = self.session['userkey']
			activity.spot = spot.key()
			activity.comment = comment_key
			activity.photo = None
			activity.put()
			ClearRecentActivitiesMemcache()
			ClearMemberRecentActivitiesMemcache(self.session["username"])
			
		except:
			logging.error("There was an error saving comment for spot id: %s" % str_spot_id)
			url += "&comment_error=%s" % ("Error saving comment.")
			self.redirect(url)
			return

		self.redirect(url)

class AddSkateSessionHandler(webapp.RequestHandler):
	def post(self):
		self.session = Session()
#		if 'spotid' not in self.session:	
#			doRender(self, 'spot.html', {'skate_session_error': 'Cant find spot id.'})
#			return

#		if 'username' not in self.session:
#			url = '/spot?id' + str(self.session['spotid'])
#			doRender(self, url, {'skate_session_error': 'Must be logged in to add a new skate session.'})
#			return

		str_spot_key = self.request.get("spot")
		spot_key = db.Key(str_spot_key)

#		logging.info("got spot_key: " + str_spot_key)
		skate_session_text = cgi.escape(self.request.get("skate_session"))
#		if skate_session_text == '':
#			logging.info("got empty skate session")
#			doRender(self, 'spot.html', {'skate_session_error': 'Please enter a skate session.'})
#			return

		# Add a "SkateSession"
		skate_session = SkateSession()
		skate_session.spot = spot_key
		skate_session.added_by = self.session['userkey']
		skate_session.title = skate_session_text
		skate_session_key = skate_session.put()

		# Add an "add_skate_session" activity
		activity = Activity(type='spot_skate_session')
		activity.member = self.session['userkey']
		activity.spot = spot_key
		activity.comment = skate_session_key
		activity.photo = None
		activity.put()
		ClearRecentActivitiesMemcache()
		ClearMemberRecentActivitiesMemcache(self.session["username"])

		self.redirect('/spot?id=' + str_spot_key)

"""
Image Handler
=============
"""
class ImageHandler(webapp.RequestHandler):
	def return_image(self, img_data):
		content_type, width, height = utils.getImageInfo(img_data)
		self.response.headers["Content-Type"] = content_type
		self.response.out.write(img_data)
		return
		
	def get(self):
		size = self.request.get("size")
		
		img_id = self.request.get("id")
		if img_id:
			image_data = GetImage(int(img_id), size)
			if image_data:
				self.return_image(image_data)
			else:
				self.error(404)
			return	

		""" NOT WORKING YET
		featured_spot_id = self.request.get("featured_spot")
		if featured_spot_id:
			logging.info("ImageHandler() getting featured_spot")
			spot = GeoSpot.get_by_id(int(featured_spot_id))
			if spot:
				composite_tuple = []
				photos_query = Photo.all().filter('spot =', spot.key()).order('-timestamp')
				photos = photos_query.fetch(5)
				count = 0
				for photo in photos:
					image_data = GetImage(photo.image.key().id(), "original")
					if image_data:
						if len(composite_tuple):
							composite_tuple = composite_tuple, (image_data, 0, 0, 1.0, images.TOP_LEFT + count)
						else:
							composite_tuple = (image_data, 0, 0, 1.0, images.TOP_LEFT + count)
							
						count = count + 1
						composite_tuple = composite_tuple, (image_data, 0, 0, 1.0, images.TOP_LEFT + count)
						count = count + 1
						composite_tuple = composite_tuple, (image_data, 0, 0, 1.0, images.TOP_LEFT + count)
						count = count + 1

				logging.info(composite_tuple)						
				new_image = images.composite(composite_tuple, 650, 250, 0, images.PNG)
				self.return_image(new_image)
			else:
				self.error(404)
			return
		"""
			
		avatar_username = self.request.get("avatar")
		#make sure and lowercase it
		avatar_username = avatar_username.lower()
		if avatar_username:
			que = db.Query(Member).filter('username =', avatar_username)
			ret_members = que.fetch(limit=1)
			if len(ret_members) == 1:
				member = ret_members[0]
				if member.avatar:
					img_data = GetImage(member.avatar.key().id(), size)
					self.return_image(img_data)
				else:
					if member.uid:
						self.redirect("http://graph.facebook.com/%s/picture?type=square" % (member.uid))
					else:
						self.redirect("images/no_avatar.png")
					
				return

		# special case for those users who haven't set an avatar yet.  Incoming will show "/img?id=&type=avatar"
		type = self.request.get("type")
		if type == "avatar":
			self.redirect("images/no_avatar.png")
			return

		# Couldn't find image
		self.error(404)
				
"""
RPCHandler
==========
"""
class RPCHandler(webapp.RequestHandler):
  # only allow functions defined in the RPCMethods class to be RPC'd
  def __init__(self):
    webapp.RequestHandler.__init__(self)
    self.methods = RPCMethods()
    
  def get(self):
    func = None
    action = self.request.get('action')
    if action:
      if action[0] == '_':
        self.error(403) # access denied
        return
      else:
        func = getattr(self.methods, action, None)

    if not func:
      self.error(404) # file not found
      return

    args = ()
    while True:
      key = 'arg%d' % len(args)
      val = self.request.get(key)
      #logging.info("DEBUG: RPCHandler() val: " + val)
      #logging.info(type(val))
      if val:
        args += (simplejson.loads(val),)
      else:
        break

    result = func(*args)
    self.response.out.write(simplejson.dumps(result))
    
class RPCMethods:
  def add(self, *args):
    ints = [int(arg) for arg in args]
    return sum(ints)

  def get_spot(self, *args):
	spot_id = int(args[0])
	spot = GeoSpot.get_by_id(spot_id)
	ret_val = {}
	ret_val['error'] = 0
	if spot is None:
		ret_val['error'] = 1

	ret_val['name'] = spot.name
	ret_val['desc'] = spot.desc
	ret_val['features'] = spot.features
	ret_val['tranny_features'] = spot.tranny_features
	ret_val['street_features'] = spot.street_features
	ret_val['longboard_features'] = spot.longboard_features
	ret_val['misc_features'] = spot.misc_features
		
	return ret_val
	        
  def getspots(self, *args):
    # Parameters passed in are SW GLatLng and NE GLatLng.
    # /rpc?action=getspots&arg0=22.02&arg1=-143.70&arg2=50.23&arg3=-100.63
    # /rpc?action=getspots&sw_lat=22.02&sw_lng=-143.70&ne_lat=50.23&ne_lng=-100.63
    #logging.info("DEBUG: getspots()")
    
    bounds_filter = False
    if len(args) == 4:
      bounds_filter = True
      sw_lat = float(args[0])
      sw_lng = float(args[1])
      ne_lat = float(args[2])
      ne_lng = float(args[3])

    spots = []
    if bounds_filter == True:
      spots = GeoSpot.bounding_box_fetch(
        GeoSpot.all(),
        geo.geotypes.Box(ne_lat, ne_lng, sw_lat, sw_lng),
        max_results=999)
    else:
      spots_query = GeoSpot.all()
      spots = spots_query.fetch(100)
      
    json_array = []
    for spot in spots:
      json_spot = {}
      json_spot['id'] = spot.key().id()
      json_spot['name'] = spot.name
      json_spot['latitude'] = spot.location.lat
      json_spot['longitude'] = spot.location.lon
      json_spot['description'] = spot.desc
      json_spot['features'] = spot.features
      json_spot['tranny_features'] = spot.tranny_features
      json_spot['street_features'] = spot.street_features
      json_spot['longboard_features'] = spot.longboard_features
      json_spot['misc_features'] = spot.misc_features
      json_array.append(json_spot)
      
    return json_array

  def follow(self, *args):
	# returns True/False if something changed
	# /rpc?action=follow&arg0=298&arg1=1
	# /rpc?action=follow&member_id=1234&value=1
	#logging.info("DEBUG: follow()")

	if len(args) != 2:
		logging.info("DEBUG: follow() invalid num params")
		return False
	
	member_id = int(args[0])
	follow = int(args[1])

	member = Member.get_by_id(member_id);
	if member is None:
		logging.info("DEBUG: follow() invalid member id")
		return False
		
	self.session = Session()
	if 'userkey' in self.session:
		you = Member.get(self.session['userkey'])
		if you:
			if member.username in you.following:
				# currently following
				if follow == 0:
					you.following.remove(member.username)
					you.put()
					return True
			else:
				# not currently following
				if follow:
					you.following.append(member.username)
					you.put()
					return True

	return False # nothing changed

  def favorite(self, *args):
	# returns True/False if something changed
	# /rpc?action=favorite&arg0=2322&arg1=298&arg2=1
	# /rpc?action=favorite&userid=2322&spot_id=1234&value=1
	logging.info("DEBUG: favorite()")

	ret_val = {}
	ret_val['ret'] = 0
	ret_val['fav'] = 0
	
	if len(args) != 3:
		logging.error("DEBUG: favorite() invalid num params")
		return ret_val

	user_id = int(args[0])
	spot_id = int(args[1])
	favorite = int(args[2])

	spot_favorites_array = GeoSpotFavorites.all().filter('userid =', user_id).fetch(1)
	if len(spot_favorites_array) == 0:
		logging.info("DEBUG: favorite() NO favorites found")
		if favorite:
			spot_favorites = GeoSpotFavorites(userid=user_id)
			spot_favorites.favorites.append(spot_id)
			spot_favorites.put()
			ret_val['ret'] = 1
			ret_val['fav'] = 1
			return ret_val
	else:
		logging.info("DEBUG: favorite() favorites found")
		spot_favorites = spot_favorites_array[0]
		if spot_id in spot_favorites.favorites:
			# currently in favorites
			if favorite == 0:
				spot_favorites.favorites.remove(spot_id)
				spot_favorites.put()
				ret_val['ret'] = 1
				ret_val['fav'] = 0
				return ret_val
		else:
			# not currently a favorite
			if favorite:
				spot_favorites.favorites.append(spot_id)
				spot_favorites.put()
				ret_val['ret'] = 1
				ret_val['fav'] = 1
				return ret_val
		
	return ret_val # nothing changed

  def validate_spots(self, *args):
	# returns True/False if something changed
	# /rpc?action=validate_spots&arg0=1,2,3
	# /rpc?action=validate_spots&spot_ids=<id1,id2,etc>
	logging.info("DEBUG: validate_spots()")

	if len(args) != 1:
		logging.error("DEBUG: validate_spots() invalid num params")
		return False

	"""
	str_spot_ids = args[0]

	if users.is_current_user_admin():
		spot_ids = str_spot_ids.split(',')
		for spot_id in spot_ids:
			n_spot_id = int(spot_id)
			logging.info("DEBUG: validate_spots() got %d", n_spot_id);
			
		return True
	"""
	
	return False

  def edit_spot_loc(self, *args):
	# returns True/False if something changed
	# /rpc?action=edit_spot_loc&
    # /rpc?action=edit_spot_loc&arg0=1234&arg1=-143.70&arg2=50.23
    # /rpc?action=getspots&spot_id=1234&new_lat=50.23&new_lng=-100.63
	logging.info("DEBUG: edit_spot_loc()")
	
	# Check for correct number of params
	if len(args) != 3:
		logging.error("DEBUG: edit_spot_loc() invalid num params")
		return False

	# Update spot location
	spot_id = int(args[0])
	new_lat = float(args[1])
	new_lng = float(args[2])
	spot = GeoSpot.get_by_id(spot_id)
	if spot:
		# Check permissions!!!
		self.session = Session()
		if users.is_current_user_admin() == False:
			# original creator of spot has edit access
			if 'userkey' in self.session:
				if spot.added_by.key() != self.session['userkey']:
					logging.error("DEBUG: edit_spot_loc() permission denied: %s" % (spot.name))
					return False
	
		spot.location = db.GeoPt(new_lat, new_lng)
		spot.update_location() #synchronize the entity's underlying geocell indexing properties
		spot.put()
		logging.info("DEBUG: edit_spot_loc() location updated: %s" % (spot.name))
		return True
	
	return False
	  
  def delete_spot(self, *args):
	# returns True/False if something changed
	# /rpc?action=normalize_spot_names
	if users.is_current_user_admin():
		logging.info("DEBUG: delete_spot()")
		str_spot_id = args[0]
		spot = GeoSpot.get_by_id(int(str_spot_id))
		if spot:
			#Delete any associated activity
			activities = Activity.all().filter('spot =', spot.key()).fetch(999)
			for activity in activities:
				logging.info("DEBUG: delete_spot() - deleting activity")
				activity.delete()
				
			ClearRecentActivitiesMemcache()
				
			#Delete any associated comments
			comments = Comment.all().filter('spot =', spot.key()).fetch(999)
			for comment in comments:
				logging.info("DEBUG: delete_spot() - deleting comment")
				comment.delete()

			#Delete any associated photos
			photos = Photo.all().filter('spot =', spot.key()).fetch(999)
			for photo in photos:
				image = Sk8Image.get_by_id(photo.key().id())
				if image:
					image.delete()
					
				logging.info("DEBUG: delete_spot() - deleting photo")
				photo.delete()
						
			#Delete the spot
			spot.delete()
			logging.info("DEBUG: delete_spot() - deleting geospot")
			return True
			
		return False
		
	logging.info("DEBUG: delete_spot() FAIL, must be ADMIN!!!")
	return False
  
	
  def normalize_spot_names(self, *args):
	# returns True/False if something changed
	# /rpc?action=normalize_spot_names
	if users.is_current_user_admin():
		logging.info("DEBUG: normalize_spot_names()")
		spots = GeoSpot.all().fetch(999)
		for spot in spots:
			spot.normalized_name = spot.name.lower()
			spot.put()
		return True
		
	logging.info("DEBUG: normalize_spot_names() FAIL, must be ADMIN!!!")
	return False
    
  def convert_feature_flags(self, *args):
	# returns True/False if something changed
	# /rpc?action=convert_feature_flags

	if users.is_current_user_admin():
		logging.info("DEBUG: convert_feature_flags()")
		# Admin users only please!!!
		spots = GeoSpot.all().fetch(999)
		for spot in spots:
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_MINI_RAMP:
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_HALF_PIPE
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_MINI
				spot.misc_features |= spot_features.MiscEnum.MISC_WOOD
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_SPINE_RAMP:
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_HALF_PIPE
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_SPINE
				spot.misc_features |= spot_features.MiscEnum.MISC_WOOD
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_VERT_RAMP:
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_HALF_PIPE
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_VERT
				spot.misc_features |= spot_features.MiscEnum.MISC_WOOD

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_FULL_PIPE:
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_FULL_PIPE
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_OVER_VERT

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_BOWL:
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_BOWL
				spot.misc_features |= spot_features.MiscEnum.MISC_CONCRETE
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_POOL:
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_BACKYARD_POOL
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_POOL_COPING
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_VERT
				spot.misc_features |= spot_features.MiscEnum.MISC_CONCRETE

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_CRADLE:
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_OVER_VERT

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_CLAMSHELL:
				spot.tranny_features |= spot_features.TrannyEnum.TRANNY_OVER_VERT

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_CURB:
				spot.street_features |= spot_features.StreetEnum.STREET_CURB

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_STAIRS:
				spot.street_features |= spot_features.StreetEnum.STREET_STAIRS

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_RAIL:
				spot.street_features |= spot_features.StreetEnum.STREET_RAIL

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_BANK:
				spot.street_features |= spot_features.StreetEnum.STREET_BANK

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_BENCH:
				spot.street_features |= spot_features.StreetEnum.STREET_LEDGE
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_LEDGE:
				spot.street_features |= spot_features.StreetEnum.STREET_LEDGE

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_GAP:
				spot.street_features |= spot_features.StreetEnum.STREET_GAP

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_MANUAL_PAD:
				spot.street_features |= spot_features.StreetEnum.STREET_MANUAL_PAD

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_PYRAMID:
				spot.street_features |= spot_features.StreetEnum.STREET_BANK

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_DOWNHILL:
				spot.longboard_features |= spot_features.LongboardEnum.LONGBOARD_DOWNHILL

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_FREERIDE:
				spot.longboard_features |= spot_features.LongboardEnum.LONGBOARD_FREERIDE

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_SLALOM:
				spot.longboard_features |= spot_features.LongboardEnum.LONGBOARD_SLALOM

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_INDOOR:
				spot.misc_features |= spot_features.MiscEnum.MISC_INDOOR

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_OUTDOOR:
				spot.misc_features |= spot_features.MiscEnum.MISC_OUTDOOR
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_LIGHTS:
				spot.misc_features |= spot_features.MiscEnum.MISC_LIGHTS
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_CONCRETE:
				spot.misc_features |= spot_features.MiscEnum.MISC_CONCRETE
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_WOOD:
				spot.misc_features |= spot_features.MiscEnum.MISC_WOOD

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_ENTRANCE_FEE:
				spot.misc_features |= spot_features.MiscEnum.MISC_ENTRANCE_FEE

			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_PADS_REQUIRED:
				spot.misc_features |= spot_features.MiscEnum.MISC_PADS_REQUIRED
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_BUST:
				spot.misc_features |= spot_features.MiscEnum.MISC_BUST
				
			if spot.features & spot_features.SpotFeaturesEnum.FEATURE_SKATE_SHOP:
				spot.misc_features |= spot_features.MiscEnum.MISC_SKATESHOP

			logging.info("DEBUG: convert_feature_flags(): name=%s, feature=%x, tranny=%x, street=%x, longboard=%x, misc=%x" % (spot.name, spot.features, spot.tranny_features, spot.street_features, spot.longboard_features, spot.misc_features));
			spot.put()
			
		return True

	logging.info("DEBUG: convert_feature_flags() FAIL, must be ADMIN!!!")
	return False

def get_features_array(spot):
	ret_array = []
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_QUARTER_PIPE:
		ret_array.append("Quarter Pipe")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_HALF_PIPE:
		ret_array.append("Half Pipe")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_FULL_PIPE:
		ret_array.append("Full Pipe")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_BOWL:
		ret_array.append("Bowl")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_BACKYARD_POOL:
		ret_array.append("Backyard Pool")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_MINI:
		ret_array.append("Mini")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_VERT:
		ret_array.append("Vert")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_OVER_VERT:
		ret_array.append("Over-Vert")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_SPINE:
		ret_array.append("Spine")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_METAL_COPING:
		ret_array.append("Metal Coping")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_POOL_COPING:
		ret_array.append("Pool Coping")
	if spot.tranny_features & spot_features.TrannyEnum.TRANNY_SNAKE_RUN:
		ret_array.append("Snake Run")
	
	if spot.street_features & spot_features.StreetEnum.STREET_CURB:
		ret_array.append("Curb")
	if spot.street_features & spot_features.StreetEnum.STREET_STAIRS:
		ret_array.append("Stairs")
	if spot.street_features & spot_features.StreetEnum.STREET_RAIL:
		ret_array.append("Rail")
	if spot.street_features & spot_features.StreetEnum.STREET_BANK:
		ret_array.append("Bank")
	if spot.street_features & spot_features.StreetEnum.STREET_LEDGE:
		ret_array.append("Ledge")
	if spot.street_features & spot_features.StreetEnum.STREET_GAP:
		ret_array.append("Gap")
	if spot.street_features & spot_features.StreetEnum.STREET_MANUAL_PAD:
		ret_array.append("Manual Pad")
	if spot.street_features & spot_features.StreetEnum.STREET_DITCH:
		ret_array.append("Ditch")

	if spot.longboard_features & spot_features.LongboardEnum.LONGBOARD_DOWNHILL:
		ret_array.append("Downhill")
	if spot.longboard_features & spot_features.LongboardEnum.LONGBOARD_FREERIDE:
		ret_array.append("Freeride")
	if spot.longboard_features & spot_features.LongboardEnum.LONGBOARD_SLALOM:
		ret_array.append("Slalom")
	if spot.longboard_features & spot_features.LongboardEnum.LONGBOARD_PARKING_GARAGE:
		ret_array.append("Parking Garage")

	if spot.misc_features & spot_features.MiscEnum.MISC_INDOOR:
		ret_array.append("Indoor")
	if spot.misc_features & spot_features.MiscEnum.MISC_OUTDOOR:
		ret_array.append("Outdoor")
	if spot.misc_features & spot_features.MiscEnum.MISC_LIGHTS:
		ret_array.append("Lights")
	if spot.misc_features & spot_features.MiscEnum.MISC_CONCRETE:
		ret_array.append("Concrete")
	if spot.misc_features & spot_features.MiscEnum.MISC_WOOD:
		ret_array.append("Wood")
	if spot.misc_features & spot_features.MiscEnum.MISC_SKATEPARK:
		ret_array.append("Skatepark")
	if spot.misc_features & spot_features.MiscEnum.MISC_DIY:
		ret_array.append("DIY")
	if spot.misc_features & spot_features.MiscEnum.MISC_BACKYARD:
		ret_array.append("Backyard")
	if spot.misc_features & spot_features.MiscEnum.MISC_NATURAL_TERRAIN:
		ret_array.append("Natural Terrain")
	if spot.misc_features & spot_features.MiscEnum.MISC_SKATESHOP:
		ret_array.append("Skate Shop")
	if spot.misc_features & spot_features.MiscEnum.MISC_ENTRANCE_FEE:
		ret_array.append("Entrance Fee")
	if spot.misc_features & spot_features.MiscEnum.MISC_PADS_REQUIRED:
		ret_array.append("Pads Required")
	if spot.misc_features & spot_features.MiscEnum.MISC_BUST:
		ret_array.append("Bust")

	return ret_array
	

def get_features_string(spot):
	ret_str = ""
	features_array = get_features_array(spot)
	first = True
	for feature in features_array:
		if first != True:
			ret_str += ", "
		first = False
		ret_str += feature
	
	if len(ret_str) == 0:
		ret_str = "None"
			
	return ret_str
		
"""
Profile Handler
===============
"""
class ProfileHandler(BaseHandler):
	def get(self):
		name = self.request.get('name')
		# make sure username is lowercase
		name = name.lower()
		if name:
			logging.info("got name = " + name)
			query = db.Query(Member).filter('username =', name)
			ret_members = query.fetch(limit=1)
			if len(ret_members) == 0: 
				doRender(self, 'sk8maps_index.html')
				return
				
			member = ret_members[0]
			
			member.is_following = False
			
			# is this current user?
			member.self = False
			self.session = Session()
			if 'username' in self.session:
				if self.session['username'] == member.username:
					# It's YOU doofus
					member.self = True
				else:
					# Check if you're following this person
					you = Member.get(self.session['userkey'])
					if you:
						if you.following:
							member.is_following = member.username in you.following

			member.member_since = utils.StrDate(member.join_date)
			member.display_name = utils.GetDisplayName(member)
			
			activities = GetMemberRecentActivities(member.username, member.key())
			#activities = Activity.all().filter('member =', member.key()).order('-timestamp').fetch(20)

			fav_spots = []
			user_favorites = GeoSpotFavorites.all().filter('userid =', member.key().id()).fetch(1)
			if len(user_favorites):
				fav_spot_ids = user_favorites[0].favorites
				if len(fav_spot_ids):
					fav_spots = GeoSpot.get_by_id(fav_spot_ids)
			
			template_values = {
				'activities': activities,
				'member': member,
				'fav_spots': fav_spots
				}
			doRender(self, "sk8maps_profile.html", template_values)
			return

		doRender(self, "sk8maps_index.html")

"""
Edit Profile Handler
====================
"""
class EditProfileHandler(BaseHandler):
	def get(self):
		self.session = Session()
		if 'username' not in self.session:
			doRender(self, 'sk8maps_edit_profile.html', {'error': 'Must be logged in to edit profile.'})
			return

		member = Member.get(self.session['userkey'])
		if member:
			member.member_since = utils.StrDate(member.join_date)
			template_values = {
				'member': member
				}
			doRender(self, "sk8maps_edit_profile.html", template_values)
			return

		doRender(self, "sk8maps_index.html")

	def post(self):
		self.session = Session()
		if 'username' not in self.session:
			doRender(self, 'sk8maps_edit_profile.html', {'error': 'Must be logged in to edit profile.'})
			return

		member = Member.get(self.session['userkey'])
		if member is None:
			doRender(self, 'sk8maps_edit_profile.html', {'error': 'Error reading member.'})
			return

		# last read member values in case of a form error
		template_values = {
			'member': member
			}

		skating_since = int(self.request.get("started_skating_in"))
		lives_in = self.request.get("livesin")
		biography = self.request.get("info")
		cur_pwd = self.request.get("cur_pwd")
		new_pwd = self.request.get("new_pwd")
		confirm_pwd = self.request.get("confirm_pwd")
		new_avatar_data = self.request.get("avatar")
		nickname = self.request.get("nickname")
		
		logging.info("EditProfile: \n\n\tSince=%i,\n\tlives_in=%s, \n\tbio=%s, \n\tcur_pwd=%s, \n\tnew_pwd=%s, \n\tconfirm_pwd=%s" % (skating_since, lives_in, biography, cur_pwd, new_pwd, confirm_pwd))

		member.skating_since = skating_since
		member.lives_in = lives_in
		member.biography = biography
		member.nickname = nickname
		
		# may have updated nickname so make sure and update session data for "displayname"
		self.session["displayname"] = utils.GetDisplayName(member)
		
		# check for update password
		if len(cur_pwd) and len(new_pwd) and len(confirm_pwd):
			logging.info("EditProfile: Got current password")
		
			salt_pwd = member.salt + cur_pwd
			hashed_pwd = utils.CreateSHA1(salt_pwd)
			if (hashed_pwd == member.pwd):
				logging.info("EditProfile: Password checked out, checking for new one...")
				# password checks out, make sure they entered a valid new one
				if new_pwd != confirm_pwd:
					template_values['form_error'] = 'New passwords don\'t match.'
					doRender(self, 'sk8maps_edit_profile.html', template_values)
					return
				else:
					# update password fields
					member.salt = utils.CreateSalt()
					salt_pwd = member.salt + new_pwd
					member.pwd = utils.CreateSHA1(salt_pwd)
					
			else:
				template_values['form_error'] = 'Current password invalid.'
				doRender(self, 'sk8maps_edit_profile.html', template_values)
				return

		# check for updated avatar
		if new_avatar_data != '':
			if member.avatar:
				UpdateImage(member.avatar.key().id(), new_avatar_data)
			else:
				member.avatar = AddImage(new_avatar_data)

		# actually update the member in datastore now				
		member.put()

		# data changed, clear out memcache
		ClearMemberMemcache(member.key().id())

		url = "/profile?name=%s" % (member.username)
		self.redirect(url)

"""
Contact Handler
===============
"""
class ContactHandler(BaseHandler):
	def get(self):
		doRender(self, "sk8maps_contact.html")
		
"""
FAQ Handler
===========
"""
class FAQHandler(BaseHandler):
	def get(self):
		doRender(self, "sk8maps_faq.html")


"""
Ad Handler
==========
"""
class AdHandler(webapp.RequestHandler):
	def get(self):
		self.redirect("http://www.google.com")

"""
Nitro Achievement Callback
==========================
"""
class NitroAchievementCallback(webapp.RequestHandler):
	def get(self):
		logging.info("NitroAchievementCallback")
		logging.info(self.response)
		self.response.out.write("nitro_callback")

		
"""
Web App Main
============
"""
application = webapp.WSGIApplication(
                                     [('/', MainPage),
                                      ('/test', MainPageTest),
                                      ('/map_index', MapIndexHandler),
                                      ('/map', MapHandler),
                                      ('/spots', SpotsHandler),
                                      ('/members', MembersHandler),
                                      ('/login', LoginHandler),
                                      ('/logout', LogoutHandler),
                                      ('/signup', SignUpHandler),
									  ('/change_avatar', ChangeAvatarHandler),
									  ('/addspot', AddSpotHandler),
									  ('/spot', SpotHandler),
									  ('/edit_spot', EditSpotHandler),
									  ('/addphoto', AddPhotoHandler),
									  ('/addcomment', AddCommentHandler),
									  ('/addskatesession', AddSkateSessionHandler),
									  ('/img', ImageHandler),
									  ('/rpc', RPCHandler),
									  ('/profile', ProfileHandler),
									  ('/edit_profile', EditProfileHandler),
									  ('/contact', ContactHandler),
									  ('/faq', FAQHandler),
									  ('/ad', AdHandler),
									  ('/nitro_achievement_callback', NitroAchievementCallback),
                                      ('/admin', AdminPage)],
                                     debug=True)

def real_main():
  run_wsgi_app(application)

def profile_main_html():
    # This is the main function for profiling
    # We've renamed our original main() above to real_main()
    import cProfile, pstats
    prof = cProfile.Profile()
    prof = prof.runctx("real_main()", globals(), locals())
    print "<pre>"
    stats = pstats.Stats(prof)
    stats.sort_stats("time")  # Or cumulative
    stats.print_stats(80)  # 80 = how many to print
    # The rest is optional.
    # stats.print_callees()
    # stats.print_callers()
    print "</pre>"

def profile_main_log():
    # This is the main function for profiling
    # We've renamed our original main() above to real_main()
    import cProfile, pstats, StringIO
    prof = cProfile.Profile()
    prof = prof.runctx("real_main()", globals(), locals())
    stream = StringIO.StringIO()
    stats = pstats.Stats(prof, stream=stream)
    stats.sort_stats("time")  # Or cumulative
    stats.print_stats(80)  # 80 = how many to print
    # The rest is optional.
    # stats.print_callees()
    # stats.print_callers()
    logging.info("Profile data:\n%s", stream.getvalue())    
    
    
if __name__ == "__main__":
  real_main()
  #profile_main_html()
  #profile_main_log()